package cn.pugle.base.net.socket;

import cn.pugle.base.net.socket.client.CONS;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用NIO的Server 非阻塞，线程池 处理接收到的socketChannel
 * 没用Selector(感觉有两个地方涉及用不用selector来多路复用，一个是ServerSocketChannel accept的时候，一个是接收到的SocketChannel读数据的时候)
 *
 * serveSocketChannel的accept方法，不会阻塞直接返回null 或返回socketChannel。
 * SocketChannel的rend方法,不会阻塞, 只把当前可读的数据放入buffer即返回。
 *
 *
 * @author tzp
 * @date 2015/12/7
 */
public class ChannelBufferServer {
    static ExecutorService threadPool;

    static {
        threadPool = Executors.newFixedThreadPool(10);
    }

    public static volatile boolean printflag = true;//控制主线程打印信息。

    public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.bind(CONS.inetSocketAddress);
            ssc.configureBlocking(false);
            while (!Thread.interrupted()) {//这里这个非阻塞加循环是不好的, 浪费cpu
                SocketChannel acceptChanel = ssc.accept();
                if (null == acceptChanel) {
                    try {
                        if (printflag)
                            System.out.println("- accept() return null - its not blocking, you can do something else");
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("- accept() someone");
                    printflag = false;
                    threadPool.execute(new MyChannelWorker(acceptChanel));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    static class MyChannelWorker implements Runnable {
        SocketChannel channelToTreat;

        public MyChannelWorker(SocketChannel channelToTreat) {
            this.channelToTreat = channelToTreat;
        }

        @Override
        public void run() {
            try {
                channelToTreat.configureBlocking(false);
                System.out.println("+ begin treat socketChannel, remoteAddress" + channelToTreat.getRemoteAddress().toString());
                System.out.println("+ isConnected: " + channelToTreat.isConnected());
                System.out.println("+ isConnectionPending: " + channelToTreat.isConnectionPending());

                //NIO的read，buffer改大改小，都是只把目前可得到的数据（如何定义目前可得到？答就是目前可得到）读入。
                ByteBuffer buffer = ByteBuffer.allocate(2 * 1024 * 1024);

                int readNum = 0;
                StringBuilder stringBuilder = new StringBuilder();
                while (stringBuilder.lastIndexOf(CONS.PROTOCOL_TAIL) == -1 && readNum != -1) {
                    //client开始现睡5s，下面这个就输出0. 不阻塞
                    readNum = channelToTreat.read(buffer);
                    System.out.println("= " + readNum + " bytes read");

                    int curPos = buffer.position();
                    System.out.println("= " + curPos + " current position");

                    buffer.flip();//ByteBuffer的神奇操作
                    byte[] dst = new byte[curPos];
                    buffer.get(dst);
                    String strRece = new String(dst, CONS.CHARSET_NAME);
                    System.out.println("= receive: " + strRece);
                    stringBuilder.append(strRece);
                    buffer.clear();

                    System.out.println("= its not blocking, you can do something else");
                    Thread.sleep(30 * 1000);
                    //睡了30s，醒来之后发现buffer的position还是10，buffer里还是只有"message A".
                    // 而实际上50s client 已经发送完了 ABCD了，这些消息在哪？ 在管道里，再read就有了.
                    // 实际上这段时间，系统(kernal)确实在进行读，只不过没给到buffer.参见笔记阻塞非阻塞IO Unix网络
                    curPos = buffer.position();
                    System.out.println("= " + curPos + " current position");
                }

                System.out.println("+ end treat" + channelToTreat.getRemoteAddress().toString());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (channelToTreat != null) {
                        channelToTreat.close();
                    }
                    printflag = true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
